package com.espirit.eap;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.apache.log4j.Logger;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.springframework.stereotype.Service;

import com.espirit.eap.manager.I18n;
import com.googlecode.cswish.exception.ApplicationException;
import com.googlecode.cswish.model.PageInfo;
import com.googlecode.cswish.oosearch.QLInfo;
import com.googlecode.cswish.struts.spring.GenericService;
import com.googlecode.cswish.util.ClassUtil;
import com.googlecode.cswish.util.bean.ModelUtil;

@Service
public class ValidationUtil {
	
	private final static Logger logger = Logger.getLogger(ValidationUtil.class);
	
	@Resource
	private GenericService genericService;
	
	@Resource
	private AppExceptionUtils appExceptionUtils;
	
	@PersistenceContext(unitName="espirit")
	protected EntityManager em;
	
	/*
	 * @param propertyDuplicate:from which judge whether exists duplicate data from db
	 */
	public <T> T checkAndAdd(T model) {
		if (model == null) return null;
		if (model instanceof ICheckModelDuplicateInfo){
			String[] properties = ((ICheckModelDuplicateInfo)model).gatherProperties();
			List result = existsModel(model,properties);
			if (result != null){
				return (T)result.get(0);
			}
		}
		return genericService.add(model);
	}
	
	public void checkNotNulllModel(ICheckModelNotNullInfo model) {
		String[] properties = model.gatherNotNullProperties();
		String property = existsNullModelProperty(model,properties);
		if (property != null){
			throw new ApplicationException("errors.commonPropertyNotNull", property);
//			appExceptionUtils.raiseError(model, property,"errors.commonPropertyNotNull");
		}
	}
	
	private <T> String existsNullModelProperty(T model , String[] properties){
		for(String property : properties){
			Object value = ModelUtil.getProperty(model, property);
			if (value == null){
				return property;
			} else {
				Class<?> type = value.getClass();
				if (ClassUtil.isCollection(type)){
					Collection coll = (Collection) value;
					if (coll.size() == 0) {
						return property;
					}
				}
			}
		}
		return null;
	}
	
//	@Transactional(propagation=Propagation.REQUIRES_NEW)
	public  void checkDuplicateModel(ICheckModelDuplicateInfo model){
		if (model == null) return;
		String[] properties = model.gatherProperties();
		if (properties == null) return;
		List result = existsModel(model, properties);
		if (result  != null && result.size() > 0){
			// TODO: which property?
			throw new ApplicationException("重复属性", properties[0]);
//			appExceptionUtils.raiseError(model, properties[0], "重复属性");
		}
	}
	
	public <T> List<T> existsModel(T model, String[] properties){
		if (model == null || properties == null || properties.length == 0) {
			return null;
		}
		
		// check model and skip the null value
		List<Object[]> checkValues = new ArrayList<Object[]>(properties.length);
		List<String[]> checkPropertiess = new ArrayList<String[]>(properties.length);
		boolean hasValue = false;
		for (String property : properties) {
			int index = property.indexOf('+');
			if (index == -1) {			// most of cases is the simple property
				Object value = ModelUtil.getProperty(model, property);
				if (value != null) {
					checkValues.add(new Object[] {value});
					checkPropertiess.add(new String[] {property});
					hasValue = true;
				}
			} else {
				int lastIndex = 0;
				List<Object> theValues = new ArrayList<Object>();
				List<String> theProperties = new ArrayList<String>();
				int len = property.length();
				do {
					if (index == -1) {
						index = len;
					}
					String chkProperty = property.substring(lastIndex, index);
					Object chkValue = ModelUtil.getProperty(model, chkProperty);
					if (chkValue != null) {
						hasValue = true;
					}
					// notice: the 'and' logic treat the null value as a necessary check condition
					theValues.add(chkValue);
					theProperties.add(chkProperty);
					lastIndex = index + 1;
					index = property.indexOf('+', lastIndex);
				} while (lastIndex < len);
				
				if (hasValue) {
					checkValues.add(theValues.toArray(new Object[theValues.size()]));
					checkPropertiess.add(theProperties.toArray(new String[theProperties.size()]));
				}
			}
		}
		if (!hasValue) {
			return null;
		}
		
		// build the sql
		List<Object> params = new ArrayList<Object>();
		StringBuilder hql = new StringBuilder().append("from ").append(ClassUtil.getClassName(model)).append(" a");
		for (int i = 0, len = checkPropertiess.size(); i < len; i++) {
			Object[] values = checkValues.get(i);
			String[] props = checkPropertiess.get(i);
			if (values != null) {
				if (params.isEmpty()) {
					hql.append(" where (");
				} else {
					hql.append(" or ");
				}
				
				hql.append('(');
				for (int j = 0; j < values.length; j++) {
					if (j != 0) {
						hql.append(" and ");
					}
					if (values[j] instanceof String) {
						String str = (String) values[j];
						hql.append("a.").append(props[j]).append("=?");
						params.add(str);
					} else if (values[j] == null) {
						hql.append("a.").append(props[j]).append(" is null");
					} else {
						hql.append("a.").append(props[j]).append("=?");
						params.add(values[j]);
					}
				}
				hql.append(')');
			}
		}
		hql.append(")");
		
		FlushMode oldMode = getSession().getFlushMode();
		try {
			getSession().setFlushMode(FlushMode.MANUAL);
			QLInfo qlInfo = new QLInfo(hql.toString(), true);
			qlInfo.setParams(params);
			List<T> results = (List<T>)genericService.searchByQl(qlInfo, false).getData();
			
			// check the duplicated value
			Object idValue = ModelUtil.getSimpleProperty(model, "id");
			if (idValue != null) { //update
				// a.id != ?
				for (int i = results.size() - 1; i >= 0; i--) {
					Object result = results.get(i);
					Object resultId = ModelUtil.getSimpleProperty(result, "id");
					if (idValue.equals(resultId)) {
						results.remove(i);
					}
				}
			}
			
			return results.isEmpty() ? null : results;
		} catch (Exception e) {
			logger.debug("check duplicated property", e);
		} finally {
			getSession().setFlushMode(oldMode);
		}
		return null;
	}
	
	public PageInfo searchAll(QLInfo qlInfo){
		FlushMode oldMode = getSession().getFlushMode();
		try {
			getSession().setFlushMode(FlushMode.MANUAL);
			PageInfo results = genericService.searchByQl(qlInfo, false);
			return results;
		} catch (Exception e) {
			logger.debug("check duplicated property", e);
		} finally {
			getSession().setFlushMode(oldMode);
		}
		return null;
	}
	
	public PageInfo searchAll(Object model){
		FlushMode oldMode = getSession().getFlushMode();
		try {
			getSession().setFlushMode(FlushMode.MANUAL);
			PageInfo results = genericService.searchAll(model);
			return results;
		} catch (Exception e) {
			logger.debug("check duplicated property", e);
		} finally {
			getSession().setFlushMode(oldMode);
		}
		return null;
	}
	public Session getSession(){
		return (Session)em.getDelegate();
	}
}
